package sw6.girafplaceclient;

import girafPlace.lib.Application;

import java.io.*;
import java.net.*;
import java.util.ArrayList;

import android.app.Activity;
import android.content.*;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.os.Environment;
import org.apache.http.util.ByteArrayBuffer;
import sw6.admin.R;
import sw6.launcher.ApplicationInfoProvider;
import sw6.launcher.TableInfo.Applications;

/**
 * Shows the specific application, and its details. Allows to install, 
 * uninstall and update the application.
 * @author SW6C
 */
public class AppDetailsActivity extends Activity {
	/** The application to show. Is provided through the APP extra. */
	protected Application app;

	/** The default debug tag for the girafPlaceClient package. */
	private final String tag = "GirafPlaceClient";

	/** The install state of the application. */
	private State state = State.NEWAPP;

	/** The download progress of the application, in percentages. */
	private int progress;

	/** BrodcastReciever for installs. */
	private BroadcastReceiver installReciever;

	/** BroadcastReciever for uninstalls. */
	private BroadcastReceiver uninstallReciever;

	/** 
	 * The version of the app when installed. 
	 * If the app is not installed it is -1. */
	private int installedVersion = -1;

	
	@Override
	/**
	 * Called when this activity is started. Initiates and sets up the application
	 * in regard to its State. And download the application's icon.
	 */
	protected void onCreate(Bundle savedInstanceState) {
		app = (Application)getIntent().getExtras().get("APP");
		setContentView(R.layout.appdetails);
		setupState();
		setupBroadcast();
		new DownloadIcon().execute();
		super.onCreate(savedInstanceState);
	}

	/**
	 * Sets up the state according to the installed version of the application. 
	 * Will get the installed version from the Package Manager.
	 */
	private void setupState() {
		if(state == State.DOWNLOADING && progress != 100)
			return;
		try {
			PackageInfo info = getPackageManager().getPackageInfo(app.getPackage(), 0);	
			installedVersion = info.versionCode;
			if(installedVersion != app.getVersion())
				state = State.UPDATEAPP;
			else
				state = State.OLDAPP;
		} catch(NameNotFoundException e) {
			installedVersion = -1;
			state = State.NEWAPP;
		}
		changeState();
	}

	/**
	 * Creates and registers the broadcast receivers for installing 
	 * and uninstalling an application.
	 * 
	 */
	private void setupBroadcast() {
		installReciever = new BroadcastReceiver() {		
			@Override
			public void onReceive(Context context, Intent intent) {
				int uid = (Integer) intent.getExtras().get(Intent.EXTRA_UID);
				Log.d(tag, getPackageManager().getNameForUid(uid) + app.getPackage());
				if(getPackageManager().getNameForUid(uid).compareTo(app.getPackage()) == 0) {
					try {
						installedVersion = getPackageManager().getPackageInfo(app.getPackage(), 0).versionCode;
					} catch (NameNotFoundException e1) {
						Log.wtf(tag, "A newly installed package has not been found.");
					}
					state = State.OLDAPP;
					changeState();
					
					ContentValues values = new ContentValues();
					
					try {
						ByteArrayOutputStream stream = new ByteArrayOutputStream();
						ObjectOutputStream out = new ObjectOutputStream( stream );

						out.writeObject(app);
						out.flush();

						values.put(Applications.APPLICATION_OBJECT, stream.toByteArray());

						stream.close();
						out.close();
					} catch (IOException e) {
						Log.e(tag,e.getMessage());
					}
					
					values.put(Applications.PACKAGE_NAME, app.getPackage());
					
					if(intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
						getContentResolver().update(ApplicationInfoProvider.CONTENT_URI, values, "packageName = '?'", new String[] { app.getPackage()});

					} else {
						try{
							ByteArrayOutputStream stream = new ByteArrayOutputStream();
							ObjectOutputStream out = new ObjectOutputStream( stream );

							ArrayList<String> blackList = new ArrayList<String>();
							out.writeObject(blackList);
							out.flush();

							values.put(Applications.SSID_BLACKLIST, stream.toByteArray());

							stream.close();
							out.close();
						} catch (Exception e) {
							Log.e(tag,e.getMessage());
						}

						getContentResolver().insert(ApplicationInfoProvider.CONTENT_URI, values);

						Log.d(tag,String.format("The application %1$s has been installed.",getPackageManager().getNameForUid(uid)));

					}
				}
			}
		};

		uninstallReciever = new BroadcastReceiver() {		
			/**
			 * Cleans up the UI, after an application has been uninstalled.
			 */
			@Override
			public void onReceive(Context context, Intent intent) {
				Log.d(tag, intent.getData().toString());
				if(intent.getData().toString().compareTo("package:" + app.getPackage()) == 0) {
					installedVersion = -1;
					state = State.NEWAPP;
					changeState();

					// SW6B - We have moved this code into the sw6.admin.database.DatabaseHelper.deleteApp(String appName) method because
					// 		  this method is called by sw6.admin.PackageHandler if an app is removed from the Android device. This fix
					//		  the bug where the database is incorrect if the user uninstall application from the Android system menu.
					// getContentResolver().delete(ApplicationInfoProvider.CONTENT_URI, Applications.PACKAGE_NAME + " = '" + app.getPackage() + "'", null);

					Log.d(tag,"An application has been removed.");
				}
			}
		};
		
		IntentFilter installIntent = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
		installIntent.addDataScheme("package");

		IntentFilter uninstallIntent = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
		uninstallIntent.addDataScheme("package");

		registerReceiver(installReciever,installIntent);
		registerReceiver(uninstallReciever,uninstallIntent);
	}
	/**
	 * Cleans up the broadcast receivers, before this activity is destroyed.
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		unregisterReceiver(installReciever);
		unregisterReceiver(uninstallReciever);
	}

	@Override
	protected void onResume() {
		super.onResume();
		setupState();
		
	}

	/**
	 * Changes the state of the GUI according to the application's State. 
	 * Will check the installed version to verify the set state, 
	 * and change accordingly.
	 */
	protected void changeState(){
		switch(state) {
		case NEWAPP:
			((TextView)findViewById(R.id.NameText)).setText(app.getName());
			TextView descText = (TextView)findViewById(R.id.DescriptionText);
			descText.setText(app.getDescription());
			((Button)findViewById(R.id.button)).setText("Install");
			((ProgressBar)findViewById(R.id.progressBar)).setVisibility(View.GONE);
			((ProgressBar)findViewById(R.id.progressBar)).setProgress(0);
			((Button)findViewById(R.id.button)).setEnabled(true);

			((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
				
				/** Downloads and installs the current selected application. */
				@Override
				public void onClick(View v) {
					new DownloadApk().execute();
				}
			});
			break;
		case UPDATEAPP:
			((TextView)findViewById(R.id.NameText)).setText(app.getName());
			((TextView)findViewById(R.id.DescriptionText)).setText(app.getDescription());
			((Button)findViewById(R.id.button)).setText("Update");
			((ProgressBar)findViewById(R.id.progressBar)).setVisibility(View.GONE);
			((ProgressBar)findViewById(R.id.progressBar)).setProgress(0);
			((Button)findViewById(R.id.button)).setEnabled(true);

			((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {

				/** Downloads and installs the newest available version of the current selected application. */
				@Override
				public void onClick(View v) {
					new DownloadApk().execute();
				}

			});
			break;
		case DOWNLOADING:
			((TextView)findViewById(R.id.NameText)).setText(app.getName());
			((TextView)findViewById(R.id.DescriptionText)).setText("Description: " + app.getDescription());
			((Button)findViewById(R.id.button)).setText("Installing");
			((Button)findViewById(R.id.button)).setEnabled(false);

			((ProgressBar)findViewById(R.id.progressBar)).setVisibility(View.VISIBLE);
			((ProgressBar)findViewById(R.id.progressBar)).setProgress(progress);

			break;
		case OLDAPP:
			((TextView)findViewById(R.id.NameText)).setText(app.getName());
			((TextView)findViewById(R.id.DescriptionText)).setText("Description: " + app.getDescription());
			((Button)findViewById(R.id.button)).setText("Uninstall");
			((Button)findViewById(R.id.button)).setEnabled(true);

			((ProgressBar)findViewById(R.id.progressBar)).setVisibility(View.GONE);
			((ProgressBar)findViewById(R.id.progressBar)).setProgress(0);

			((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {

				/** Uninstalls the current selected application. */
				@Override
				public void onClick(View v) {
					uninstallApp();
				}
			});
			break;
		default:
			break;

		}
	}

	/**
	 * Async task for downloading the icon of the application, from the server. 
	 * @author SW6C
	 *
	 */
	private class DownloadIcon extends AsyncTask<Void, Void, String> {

		@Override
		protected void onPreExecute() {
			Log.d(tag, "Preparing to start the download icon task");
			super.onPreExecute();
		}

		/**
		 * Downloads the icon from GirafPlace to GirafPlaceClient on the device.
		 */
		@Override
		protected String doInBackground(Void... voids) {
			try {
				Log.d(tag,"Starting the download task, first by building the string");
				String urlString = "http://girafplace.lcdev.dk/icos/";
				urlString = urlString + Integer.toString(app.getId()) + ".png"; // appName.apk

				Log.d(tag,String.format("The string is: %1$s",urlString));

				URL url = new URL(urlString);

				File file = new File(getCacheDir(), app.getName() + ".png");

				Log.d(tag,"Opening connection");
				URLConnection ucon = url.openConnection();

				ucon.connect();

				double size = ucon.getContentLength();
				Log.d(tag,String.format("The app has a reported size of %1$f", size));

				BufferedInputStream bis = new BufferedInputStream(ucon.getInputStream());

				ByteArrayBuffer baf = new ByteArrayBuffer(200);

				Log.d(tag,"Starting to read");
				int current = 0;
				while((current = bis.read()) != -1 && !isCancelled()) {
					baf.append((byte) current);
				}

				FileOutputStream fos = new FileOutputStream(file);
				fos.write(baf.toByteArray());
				fos.close();

				if(isCancelled())
					return null;
				else
					return file.getPath();
			} catch (Exception e) {
				Log.e(tag,e.getMessage());
			}
			return null;
		}

		/** Assigns the icon to the application, after it has been downloaded. */
		@Override
		protected void onPostExecute(String result) {	
			if(result != null) {
				((ImageView)findViewById(R.id.imageView1)).setImageDrawable(new BitmapDrawable(result));
			}
		}
	}

	/**
	 * An asynchronous task, for downloading and initiating the installation 
	 * of the application. 
	 * @author SW6C
	 */
	private class DownloadApk extends AsyncTask<Void, Double, String> {
		/** Prepares the client for an incoming download. */
		@Override
		protected void onPreExecute() {
			Log.d(tag, "Preparing to starting the download task");
			progress = 0;
			state = State.DOWNLOADING;
			changeState();

			super.onPreExecute();
		}
		
		/** Downloads the application, and informs the user of the progress. */
		@Override
		protected String doInBackground(Void... voids) {
			try {
				Log.d(tag,"Starting the download task, first by building the string");
				String urlString = "http://girafplace.lcdev.dk/installs/";
				urlString = urlString + Integer.toString(app.getId()) + ".apk"; // appName.apk


				Log.d(tag,String.format("The string is: %1$s",urlString));

				URL url = new URL(urlString);

				File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), app.getName() + ".apk");

				Log.d(tag,"Opening connection");
				URLConnection ucon = url.openConnection();

				ucon.connect();

				double size = ucon.getContentLength();
				Log.d(tag,String.format("The app has a reported size of %1$f", size));

				BufferedInputStream bis = new BufferedInputStream(ucon.getInputStream());

				ByteArrayBuffer baf = new ByteArrayBuffer(200);

				Log.d(tag,"Starting to read");
				int current = 0;
				double read = 0;
				int load = 0;
				while((current = bis.read()) != -1 && !isCancelled()) {
					baf.append((byte) current);
					read++;
					load++;
					if (load == 10000) {
						publishProgress((read/size)*100);
						load = 0;
					}
				}
				
				Log.d(tag,String.format("Read %1$f bytes from the stream", read));

				FileOutputStream fos = new FileOutputStream(file);
				fos.write(baf.toByteArray());
				fos.close();

				if(isCancelled())
					return null;
				else
					return file.getPath();
			} catch (Exception e) {
				Log.e(tag,e.getMessage());
			}
			return null;
		}

		@Override
		protected void onProgressUpdate(Double... values) {
			progress = values[0].intValue();
			((ProgressBar)findViewById(R.id.progressBar)).setProgress(progress);
			super.onProgressUpdate(values);
		}
		
		@Override
		protected void onCancelled() {
			state = State.NEWAPP;
			changeState();
			super.onCancelled();
		}
		
		/**
		 * Is run after the download has completed. Creates a new intent, 
		 * which then is run and starts the installation of the 
		 * downloaded application.
		 */
		@Override
		protected void onPostExecute(String result) {
			if(result != null) {
				try {
					Intent installApp = new Intent(Intent.ACTION_VIEW);
					installApp.setDataAndType(Uri.fromFile(new File(result)),"application/vnd.android.package-archive");
					startActivity(installApp);
				} catch (ActivityNotFoundException e){
					Log.e(tag, e.getMessage());
				}
			}
			else {
				state = State.NEWAPP;
				changeState();
			}
		}
	}

	/**
	 * Uninstalls the application, if it is installed on the device. 
	 */
	public void uninstallApp(){
		try{	
			Uri packageURI = Uri.parse("package:" + app.getPackage());
			Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
			startActivity(uninstallIntent);
		} catch (ActivityNotFoundException e){
			Log.e(tag, e.getMessage());
		}
	}

	/**
	 * An enum, for the different states of the application.
	 * @author SW6C
	 *
	 */
	protected enum State {
		NEWAPP,
		UPDATEAPP,
		DOWNLOADING,
		OLDAPP;	
	}
}
